home *** CD-ROM | disk | FTP | other *** search
Wrap
/*================================================================== File: ZStringTool.cpp Contains: Tool-related code for building ZString override libraries, etc. Written by: Eric Traut Copyright: 2000-2001 Connectix Corporation This source has been placed into the public domain by Connectix Corporation. You have the right to modify, distribute or use this code without any legal limitations or finanicial/licensing requirements. Connectix is not liable for any problems that result from the use of this code. If you have comments, feedback, questions, or would like to submit bug fixes or updates to this code, please email opensource@connectix.com. ==================================================================*/ #include "ZStringTool.h" #include <stdio.h> #include <stdlib.h> #include <new> /****************************************************************** EXPLANATION: This class can be used to create a tool that scans a binary for named ZStrings. These strings should look like: <Z name=/path1/path2/path3/...>string</Z> In this example, pathN can be replaced with any strings needed to describe the enclosed string. The value named "string" in the above example should contain the actual string prototype (including escape characters). The tool allows for the scanning of one or two binaries (Note that the second "binary" may be a text file in practice.) The first binary is the application that contains the ZStrings. The second binary will generally be the last output file from this tool. By scanning both files, the tool can determine whether any strings have been modified, added, or deleted since the last time the tool was run. ******************************************************************/ /*------------------------------------------------------------------ ZStringTool ------------------------------------------------------------------*/ ZStringTool::ZStringTool() { memset(mZStringToolHash, 0, sizeof(mZStringToolHash)); mStringRegisterCount = 0; mStringLengthSum = 0; mSortedList = NULL; } /*------------------------------------------------------------------ ~ZStringTool ------------------------------------------------------------------*/ ZStringTool::~ZStringTool() { if (mSortedList != NULL) delete[] mSortedList; for (Z_UInt32 hashTableIndex = 0; hashTableIndex < kZDictionaryHashEntries; hashTableIndex++) { ZToolEntry * curEntry; ZToolEntry * nextEntry; curEntry = mZStringToolHash[hashTableIndex]; while (curEntry != NULL) { nextEntry = curEntry->mNext; delete curEntry; curEntry = nextEntry; } } } /*------------------------------------------------------------------ LookUpString ------------------------------------------------------------------*/ ZToolEntry * ZStringTool::LookUpString( const ZStringParseInfo & inParseInfo) { Z_UInt32 entryIndex = ZStringDictionary::HashString(inParseInfo.fNameStr, inParseInfo.fNameStrLen); ZToolEntry * curEntry = mZStringToolHash[entryIndex]; while (curEntry != NULL) { // Do the lengths match? if (curEntry->mParseInfo.fNameStrLen == inParseInfo.fNameStrLen) { Z_UInt32 curCharIndex; Z_Boolean stringsMatch = true; for (curCharIndex = 0; curCharIndex < curEntry->mParseInfo.fNameStrLen; curCharIndex++) { if (curEntry->mParseInfo.fNameStr[curCharIndex] != inParseInfo.fNameStr[curCharIndex]) { stringsMatch = false; break; } } // Did we find a match? If so, return the string. if (stringsMatch) return curEntry; } curEntry = curEntry->mNext; } return NULL; } /*------------------------------------------------------------------ RegisterString ------------------------------------------------------------------*/ void ZStringTool::RegisterString( const ZStringParseInfo & inParseInfo, ZStringToolState inState) { try { ZToolEntry * newEntry = new ZToolEntry; Z_UInt32 entryIndex = ZStringDictionary::HashString(inParseInfo.fNameStr, inParseInfo.fNameStrLen); newEntry->mParseInfo = inParseInfo; newEntry->mStringState = inState; newEntry->mNext = mZStringToolHash[entryIndex]; mZStringToolHash[entryIndex] = newEntry; mStringRegisterCount++; // If the string was badly formed, it may not have a valid length. if (inState != kStringBadlyFormed) mStringLengthSum += inParseInfo.fNamedStringLimit - inParseInfo.fNamedStringStart; } catch (...) { fprintf(stderr, "ZStringTool ran out of memory. Couldn't allocate new object."); } } /*------------------------------------------------------------------ ProcessBinaries ------------------------------------------------------------------*/ void ZStringTool::ProcessBinaries( const char * inNewStart, Z_UInt32 inNewLength, const char * inOldStart, Z_UInt32 inOldLength, const ZToolOptions & inOptions) { mProcessingNew = true; ProcessBinary(inNewStart, inNewLength, (inOldStart == NULL) ? 0 : kStringAdded, inOptions); if (inOldStart != NULL) { mProcessingNew = false; ProcessBinary(inOldStart, inOldLength, 0, inOptions); } } /*------------------------------------------------------------------ ProcessBinary This method processes an in-memory version of a binary file. The caller must read it into a single, contiguous buffer. ------------------------------------------------------------------*/ void ZStringTool::ProcessBinary( const void * inStart, Z_UInt32 inLength, ZStringToolState inInitialState, const ZToolOptions & inOptions) { const char * curCharPtr = reinterpret_cast<const char *>(inStart); const char * limitCharPtr = curCharPtr + inLength; ZStringParseInfo parseInfo; ZToolEntry * existingEntry; const char * beginningTag = (inOptions.mHasOTags ? "<O name=" : "<Z name="); // added to allow resource compares while (curCharPtr < limitCharPtr) { // Look for the start of a ZString. if (curCharPtr[0] == '<' && strncmp(curCharPtr, beginningTag, 8) == 0) { if (!ZStringParser::ParseNamedString(curCharPtr, parseInfo, false)) { if (mProcessingNew) // Indicates a ZString missing </Z> RegisterString(parseInfo, kStringBadlyFormed); } else { ZString * convertedForm; ZStringParseInfo convertedParseInfo; ZParserWarningType parserWarnings; convertedForm = new ZString; check(convertedForm != NULL); if (convertedForm == NULL) { printf("Ran out of memory"); return; } // Convert the tags in the ZString to either all numeric or all alphabetic // Returns an error when there are incorrectly formed tags if (!ZStringParser::ConvertNamedStringToTag(parseInfo, inOptions, *convertedForm, parserWarnings)) { if (mProcessingNew) RegisterString(parseInfo, kStringBadlyFormed); curCharPtr++; // advance by one because a string missing </Z> will skip over the next zstring! continue; } // Reparse the new converted form // Returns an error when there is an incorrect ZString tag form if (!ZStringParser::ParseNamedString(convertedForm->GetCString(), convertedParseInfo, false)) { if (mProcessingNew) RegisterString(parseInfo, kStringBadlyFormed); // send in the old parse info because the new one may not be set curCharPtr = parseInfo.fNamedStringLimit; // Skip over the rest of the ZString continue; } // Check for line breaks & flag this as a warning if (strpbrk(convertedForm->GetCString(), "\n\r") != NULL) { if (mProcessingNew) RegisterString(convertedParseInfo, kStringBreakWarning); // send in new parse info because the previous function returned ok curCharPtr = parseInfo.fNamedStringLimit; // Skip over the rest of the ZString continue; } // Check for high bit ASCII characters Z_Boolean hasHiASCII = false; for (Z_UInt16 i = 0; i < convertedForm->GetLength(); i++) { if (static_cast<Z_UInt8>(convertedForm->GetCString()[i]) > 127) hasHiASCII = true; } // If a limit tag exists, check the length of the data string (warnings are produced if over sized) Z_Boolean exceedsLimit = false; if (convertedParseInfo.fHasMaxDataLen) exceedsLimit = ZStringParser::CheckDataLength(convertedParseInfo); // Is there already an entry? existingEntry = LookUpString(convertedParseInfo); if (mProcessingNew) { // Derive state ZStringToolState state = inInitialState; // Indicate any warnings if ((parserWarnings & kZParser_ChangedString) != 0) state |= kStringChangeWarning; if ((parserWarnings & kZParser_FoundPossibleTag) != 0) state |= kStringPossibleTagWarning; if (exceedsLimit) state |= kStringLimitWarning; if (existingEntry != NULL) // If we already encountred this string, it's a duplicate. state |= kStringDuplicate; if (hasHiASCII || (parserWarnings & kZParser_HasHighASCII) != 0) state |= kStringHasHighASCII; if ( existingEntry == NULL || strcmp(convertedForm->GetCString(), existingEntry->mParseInfo.fValueStr) == 0 ) // ignore identical duplicates RegisterString(convertedParseInfo, state); else if (inOptions.mFlagDuplicates) // Indicate that this is a duplicate existingEntry->mStringState |= kStringDuplicate; } else { // If there's an existing string, we need to determine // whether it's the same as the new binary string. if (existingEntry != NULL) { // If the string is the same, mark the string as not being added. if (NamedStringsMatch(existingEntry->mParseInfo, convertedParseInfo)) { existingEntry->mStringState &= ~kStringAdded; } else { existingEntry->mStringState &= ~kStringAdded; existingEntry->mStringState |= kStringModifiedNew; // Derive state ZStringToolState state = kStringModifiedOld; // Indicate any warnings if ((parserWarnings & kZParser_ChangedString) != 0) state |= kStringChangeWarning; if ((parserWarnings & kZParser_FoundPossibleTag) != 0) state |= kStringPossibleTagWarning; if (exceedsLimit) state |= kStringLimitWarning; if (hasHiASCII || (parserWarnings & kZParser_HasHighASCII) != 0) state |= kStringHasHighASCII; RegisterString(convertedParseInfo, state); } } else { // We didn't find the string in the new binary, so // we'll assume it was deleted. RegisterString(convertedParseInfo, kStringDeleted); } } curCharPtr = parseInfo.fNamedStringLimit - 1; } } curCharPtr++; } } /*------------------------------------------------------------------ NamedStringsMatch ------------------------------------------------------------------*/ Z_Boolean ZStringTool::NamedStringsMatch( const ZStringParseInfo & inString1, const ZStringParseInfo & inString2) { check(inString1.fValidNamedString && inString2.fValidNamedString); // If one or the other is not valid, the strings are not equivalent. if (!inString1.fValidNamedString || !inString2.fValidNamedString) return false; // If the strings are different lengths, they differ. if (inString1.fValueStrLen != inString2.fValueStrLen) return false; for (Z_UInt16 charIndex = 0; charIndex < inString1.fValueStrLen; charIndex++) { if (inString1.fValueStr[charIndex] != inString2.fValueStr[charIndex]) return false; } return true; } /*------------------------------------------------------------------ CreateSortedStringList ------------------------------------------------------------------*/ Z_Boolean ZStringTool::CreateSortedStringList( Z_Boolean inCategorizeOutput) { mSortedList = new (std::nothrow) ZToolEntry *[mStringRegisterCount]; if (mSortedList == NULL) { fprintf(stderr, "Out of memory. Couldn't allocate sorted string list."); return false; } Z_UInt32 sortedListIndex = 0; // Copy all the entry pointers from the linked lists to the new list. for (Z_UInt32 hashTableIndex = 0; hashTableIndex < kZDictionaryHashEntries; hashTableIndex++) { ZToolEntry * curHashListPtr; curHashListPtr = mZStringToolHash[hashTableIndex]; while (curHashListPtr != NULL) { check(sortedListIndex < mStringRegisterCount); mSortedList[sortedListIndex++] = curHashListPtr; curHashListPtr = curHashListPtr->mNext; } } check(sortedListIndex == mStringRegisterCount); // Now, sort the list using qsort, either by category or only alphabetically if (inCategorizeOutput) qsort(mSortedList, mStringRegisterCount, sizeof(ZToolEntry *), CompareCategoryFunction); else qsort(mSortedList, mStringRegisterCount, sizeof(ZToolEntry *), CompareStringFunction); return true; } /*------------------------------------------------------------------ CompareToolEntry This method decides if the first string is greater than, less than or the same as the second string, based upon the state of the strings and the string contents. ------------------------------------------------------------------*/ int ZStringTool::CompareToolEntries( Z_UInt16 inState, const void * inNode1, const void * inNode2) { const ZToolEntry * zString1 = *(const ZToolEntry **)(inNode1); const ZToolEntry * zString2 = *(const ZToolEntry **)(inNode2); Z_Boolean twoIsGreater = false; // Depending on the given state, decide whether the second string // would be greater than the first string switch (inState) { case kStringDeleted: twoIsGreater = ((zString2->mStringState & (kStringAdded | kStringModifiedNew | kStringModifiedOld)) != 0); break; case kStringDuplicate: twoIsGreater = ((zString2->mStringState & (kStringAdded | kStringModifiedNew | kStringModifiedOld | kStringDeleted)) != 0); break; case kStringBadlyFormed: twoIsGreater = ((zString2->mStringState & (kStringAdded | kStringModifiedNew | kStringModifiedOld | kStringDeleted | kStringDuplicate)) != 0); break; case kStringBreakWarning: twoIsGreater = ((zString2->mStringState & (kStringAdded | kStringModifiedNew | kStringModifiedOld | kStringDeleted | kStringDuplicate | kStringBadlyFormed)) != 0); break; case kStringPossibleTagWarning: twoIsGreater = ((zString2->mStringState & (kStringAdded | kStringModifiedNew | kStringModifiedOld | kStringDeleted | kStringDuplicate | kStringBadlyFormed | kStringBreakWarning)) != 0); break; case kStringLimitWarning: twoIsGreater = ((zString2->mStringState & (kStringAdded | kStringModifiedNew | kStringModifiedOld | kStringDeleted | kStringDuplicate | kStringBadlyFormed | kStringBreakWarning | kStringPossibleTagWarning)) != 0); break; case kStringChangeWarning: twoIsGreater = ((zString2->mStringState & (kStringAdded | kStringModifiedNew | kStringModifiedOld | kStringDeleted | kStringDuplicate | kStringBadlyFormed | kStringBreakWarning | kStringPossibleTagWarning | kStringLimitWarning)) != 0); break; case kStringHasHighASCII: twoIsGreater = ((zString2->mStringState & (kStringAdded | kStringModifiedNew | kStringModifiedOld | kStringDeleted | kStringDuplicate | kStringBadlyFormed | kStringBreakWarning | kStringPossibleTagWarning | kStringLimitWarning | kStringChangeWarning)) != 0); break; } if ((zString2->mStringState & inState) != 0) // If the states are the same return CompareStringFunction(inNode1, inNode2); // order alphabetically else if (twoIsGreater) return 1; else return -1; } /*------------------------------------------------------------------ CompareCategoryFunction This method sorts the output based upon the types of messages. Messages are grouped in the following way: Added Modified New / Modified Old pairs Deleted Duplicates Badly Formed Line Break Warnings String contains a possible tag warning String Limit Exceeded Warnings String character changed warnings (when converted high ascii into tags) Has High ASCII ------------------------------------------------------------------*/ int ZStringTool::CompareCategoryFunction( const void * inNode1, const void * inNode2) { const ZToolEntry * zString1 = *(const ZToolEntry **)(inNode1); const ZToolEntry * zString2 = *(const ZToolEntry **)(inNode2); // Sort the entries based on the state of the first and second strings if ((zString1->mStringState & kStringAdded) != 0) { if ((zString2->mStringState & kStringAdded) != 0) return CompareStringFunction(inNode1, inNode2); else return -1; } else if ((zString1->mStringState & kStringModifiedNew) != 0 || (zString1->mStringState & kStringModifiedOld) != 0) { if ((zString2->mStringState & kStringModifiedNew) != 0 || (zString2->mStringState & kStringModifiedOld) != 0) { if (!zString1->mParseInfo.fValidNamedString || !zString2->mParseInfo.fValidNamedString) return 0; int order = strncmp(zString1->mParseInfo.fNameStr, zString2->mParseInfo.fNameStr, zString1->mParseInfo.fNameStrLen); if (order == 0) { if ((zString1->mStringState & kStringModifiedNew) != 0 && (zString2->mStringState & kStringModifiedOld) != 0) return -1; else if ((zString1->mStringState & kStringModifiedOld) != 0 && (zString2->mStringState & kStringModifiedNew) != 0) return 1; else return 0; } else return order; } else if ((zString2->mStringState & kStringAdded) != 0) return 1; else return -1; } else if ((zString1->mStringState & kStringDeleted) != 0) return CompareToolEntries(kStringDeleted, inNode1, inNode2); else if ((zString1->mStringState & kStringDuplicate) != 0) return CompareToolEntries(kStringDuplicate, inNode1, inNode2); else if ((zString1->mStringState & kStringBadlyFormed) != 0) return CompareToolEntries(kStringBadlyFormed, inNode1, inNode2); else if ((zString1->mStringState & kStringBreakWarning) != 0) return CompareToolEntries(kStringBreakWarning, inNode1, inNode2); else if ((zString1->mStringState & kStringPossibleTagWarning) != 0) return CompareToolEntries(kStringPossibleTagWarning, inNode1, inNode2); else if ((zString1->mStringState & kStringLimitWarning) != 0) return CompareToolEntries(kStringLimitWarning, inNode1, inNode2); else if ((zString1->mStringState & kStringChangeWarning) != 0) return CompareToolEntries(kStringChangeWarning, inNode1, inNode2); else if ((zString1->mStringState & kStringHasHighASCII) != 0) return CompareToolEntries(kStringHasHighASCII, inNode1, inNode2); else if (zString2->mStringState != kStringUnique) // First string doesn't have a problem but second string does return 1; else return CompareStringFunction(inNode1, inNode2); } /*------------------------------------------------------------------ CompareStringFunction ------------------------------------------------------------------*/ int ZStringTool::CompareStringFunction( const void * inNode1, const void * inNode2) { const ZToolEntry * zString1 = *(const ZToolEntry **)(inNode1); const ZToolEntry * zString2 = *(const ZToolEntry **)(inNode2); // If the named strings aren't valid, we can't compare them. if (!zString1->mParseInfo.fValidNamedString) return -1; else if (!zString2->mParseInfo.fValidNamedString) return 1; return strncmp(zString1->mParseInfo.fNameStr, zString2->mParseInfo.fNameStr, zString1->mParseInfo.fNameStrLen); } /*------------------------------------------------------------------ PrintEntry ------------------------------------------------------------------*/ void ZStringTool::PrintEntry( ZToolEntry & inEntry, FILE * inFile, Z_Boolean inPrintWarnings) { ZString tempString; // Print status information first // In order for the sorting by category to show up correctly, this order must be the same as the order in the sort category method if ((inEntry.mStringState & kStringAdded) != 0) fprintf(inFile, "<font color=\"#0000FF\"><font size=\"4\"><b>String Added</b></font><br>\n"); else if ((inEntry.mStringState & kStringModifiedNew) != 0) fprintf(inFile, "<font color=\"#660066\"><font size=\"4\"><b>String Modified (New Version)</b></font><br>\n"); else if ((inEntry.mStringState & kStringModifiedOld) != 0) fprintf(inFile, "<font color=\"#003333\"><font size=\"4\"><b>String Modified (Obsolete Version)</b></font><br>\n"); else if ((inEntry.mStringState & kStringDeleted) != 0) fprintf(inFile, "<font color=\"#006600\"><font size=\"4\"><b>String Deleted</b></font><br>\n"); else if ((inEntry.mStringState & kStringDuplicate) != 0) fprintf(inFile, "<font color=\"#009900\"><font size=\"4\"><b>Duplicate named string: Please rename one of them</b></font><br>\n"); else if ((inEntry.mStringState & kStringBadlyFormed) != 0) fprintf(inFile, "<font color=\"#FF0000\"><font size=\"4\"><b>Badly formed string: Please fix</b></font><br>\n"); else if ((inEntry.mStringState & kStringBreakWarning) != 0 && inPrintWarnings) fprintf(inFile, "<font color=\"#FF0099\"><font size=\"4\"><b>Warning: String contains line breaks</b></font><br>\n"); else if ((inEntry.mStringState & kStringPossibleTagWarning) != 0 && inPrintWarnings) fprintf(inFile, "<font color=\"#FF0099\"><font size=\"4\"><b>Warning: String may contain malformed tags</b></font><br>\n"); else if ((inEntry.mStringState & kStringLimitWarning) != 0 && inPrintWarnings) fprintf(inFile, "<font color=\"#FF0099\"><font size=\"4\"><b>Warning: String exceeds set limit</b></font><br>\n"); else if ((inEntry.mStringState & kStringChangeWarning) != 0 && inPrintWarnings) fprintf(inFile, "<font color=\"#FF0099\"><font size=\"4\"><b>Warning: String contained high ASCII characters that have been converted to tags</b></font><br>\n"); else if ((inEntry.mStringState & kStringHasHighASCII) != 0 && inPrintWarnings) fprintf(inFile, "<font color=\"#FF0099\"><font size=\"4\"><b>Warning: String contains High ASCII characters.</b></font><br>\n"); // Now, print the named string. if ((inEntry.mStringState & kStringBadlyFormed) != 0) { // If the string is badly formed, make sure that it has a valid length. Otherwise, 256 is used. Z_UInt16 outputLength = 256; if (inEntry.mParseInfo.fNamedStringLimit > inEntry.mParseInfo.fNamedStringStart) outputLength = inEntry.mParseInfo.fNamedStringLimit - inEntry.mParseInfo.fNamedStringStart; tempString.SetString(inEntry.mParseInfo.fNamedStringStart, outputLength); } else { check(inEntry.mParseInfo.fNamedStringLimit > inEntry.mParseInfo.fNamedStringStart); tempString.SetString(inEntry.mParseInfo.fNamedStringStart, inEntry.mParseInfo.fNamedStringLimit - inEntry.mParseInfo.fNamedStringStart); } fprintf(inFile, "%s", tempString.GetCString()); // Print the </font> tag if it is an error or it is a warning and warnings are to be printed if ((inEntry.mStringState & kAllStringErrors) != 0 || ((inEntry.mStringState & kAllStringWarnings) != 0 && inPrintWarnings)) fprintf(inFile, "</font>\n"); fprintf(inFile, "<br><br>\n"); } /*------------------------------------------------------------------ CreateOverrideDictionary ------------------------------------------------------------------*/ Z_Boolean ZStringTool::CreateOverrideDictionary( FILE * inFile) { char * dictionary; Z_UInt32 dictLength; Z_Boolean success; success = CreateOverrideDictionary(dictionary, dictLength); if (!success) return success; check(dictionary != NULL); if (fwrite(dictionary, 1, dictLength, inFile) != dictLength) { fprintf(stderr, "File error encountered when writing file."); return false; } delete [] dictionary; return true; } /*------------------------------------------------------------------ AddEntryToOverrideDictionary ------------------------------------------------------------------*/ char * ZStringTool::AddEntryToOverrideDictionary( char * inOutputPtr, const ZToolEntry & inEntry) { Z_UInt32 stringLength; if (!inEntry.mParseInfo.fValidNamedString) { fprintf(stderr, "Invalid string found. Dictionary may be invalid.\n"); } else { char *string; stringLength = inEntry.mParseInfo.fNamedStringLimit - inEntry.mParseInfo.fNamedStringStart; memcpy(inOutputPtr, inEntry.mParseInfo.fNamedStringStart, stringLength); // Find <Z name= and replace with <O name= string = strstr(inOutputPtr, "<Z name="); if (string != NULL) string[1] = 'O'; // Find </Z> and replace with </O> string = strstr(inOutputPtr, "</Z>"); if (string != NULL) string[2] = 'O'; inOutputPtr += stringLength; // Terminate the string *inOutputPtr++ = '\0'; } return inOutputPtr; } /*------------------------------------------------------------------ CreateOverrideDictionary ------------------------------------------------------------------*/ Z_Boolean ZStringTool::CreateOverrideDictionary( char * & outDictionary, Z_UInt32 & outLength) { outLength = mStringRegisterCount + mStringLengthSum + 1; outDictionary = new (std::nothrow) char[outLength]; if (outDictionary == NULL) return false; // Now, fill in the dictionary. char * curOutput = reinterpret_cast<char *>(outDictionary); for (Z_UInt32 hashTableIndex = 0; hashTableIndex < kZDictionaryHashEntries; hashTableIndex++) { ZToolEntry * curHashListPtr; curHashListPtr = mZStringToolHash[hashTableIndex]; while (curHashListPtr != NULL) { curOutput = AddEntryToOverrideDictionary(curOutput, *curHashListPtr); curHashListPtr = curHashListPtr->mNext; } } // Fill in the final byte to indicate we're done. *curOutput++ = '\0'; // Make sure we got the correct length. check(curOutput == (char *)outDictionary + outLength); return true; } /*------------------------------------------------------------------ PrintReport Returns true if everything is OK. ------------------------------------------------------------------*/ Z_Boolean ZStringTool::PrintReport( FILE * inFile, const ZToolOptions & inOptions) { Z_Boolean foundError = false; Z_Boolean hasOnlyWarnings; if (!CreateSortedStringList(inOptions.mCategorizeOutput)) return false; for (Z_UInt32 sortedListIndex = 0; sortedListIndex < mStringRegisterCount; sortedListIndex++) { hasOnlyWarnings = ((mSortedList[sortedListIndex]->mStringState & kAllStringWarnings) != 0); if (mSortedList[sortedListIndex]->mStringState != kStringUnique) // if zstring generated errors or warnings if (inOptions.mOutputWarnings || !hasOnlyWarnings) // Only found an error if we are printing warnings foundError = true; if (inOptions.mPrintErrorsOnly) // if only printing errors { if (mSortedList[sortedListIndex]->mStringState != kStringUnique) // only print those that are errors or warnings PrintEntry(*mSortedList[sortedListIndex], inFile, inOptions.mOutputWarnings); } else PrintEntry(*mSortedList[sortedListIndex], inFile, inOptions.mOutputWarnings); } if (foundError) { fprintf(stderr, "One or more warnings dumped into output. \n"); fprintf(stderr, "View output in HTML browser to see warnings.\n"); } return !foundError; }